#include "game.h"

void gameInit(void) {
    // create list
    createLinkedList();
    // guarantee clean state for matrices
    for (int y = 0; y < MAX_X; y++) {
        for (int x = 0; x < MAX_Y; x++) {
            framebuffer[y][x] = LED_DISABLE;
            //activations[y][x] = 0;
        }
    }
    // set frame count
    frames = 0;
    // wait 1 second @ 64 FPS
    framesMax = DEFAULT_MAX_FRAMES;
    // set move count
    moves = DEFAULT_MAX_MOVES - 1;
    movesMax = DEFAULT_MAX_MOVES;
    shipSize = 2;
    // init initial life
    lives = MAX_LIVES;
    
    // static object initialization for testing purposes
    /*
    for (int x = 0; x < 16; x += 1) {
        int y = 1;
        addToList(
            &list,
            createGameObject(x, y, 1, 1)
        );
    }
    */
}

void gameUpdate(void) {
    // finally, check losing condition (there is no winning)
    if (lives == 0 || lives > MAX_LIVES) {
        renderGameOver();
        return;
    }

    // we do get 12 bits, try to use them
    uint8_t conversionValue = 0xFF & ((~ADC_GetConversionValue(ADC1)) >> 4);
    // make use of random values
    framesMax = DEFAULT_MAX_FRAMES + conversionValue / 4;
    
    // remove games objects that were hit
    for (int y = 0; y < MAX_Y; y++) {
        for (int x = 0; x < MAX_X; x++) {
            // use opportunity to empty framebuffer
            framebuffer[y][x] = LED_DISABLE;

            // activations are toggled where they are queried
            // (responsibility delegated to main.c TIM15 handler)
            /*
            if (activations[y][x] == 1) {
                Node *node = list.first;
                while (node != NULL) {
                    // advanced collision detection
                    if (x >= node->obj.ulx && x <= node->obj.lrx &&
                        y >= node->obj.uly && y <= node->obj.lry)
                        removeFromList(&list, node);
                    
                    // iterate
                    node = node->next;
                }
            }
            */
        }
    }
    
    // iterate game objects, move them if need be
    if (++frames >= framesMax) {
        Node *node = list.first;
        while (node != NULL) {
            // move object downwards
            ++(node->obj.uly);
            ++(node->obj.lry);

            // remove objects that collide with the ground
            // (consider upper pixels)
            if (node->obj.uly >= MAX_Y) {
                removeFromList(&list, node);
                --lives;
            }

            // iterate
            node = node->next;
        }
        
        // spawn new game object as well 
        if (++moves >= movesMax) {
            // seeded random generation using ADC values
            uint8_t randomShipSizeX = ((0xF & (0xD3 ^ conversionValues[1])) % 3) + 1;
            uint8_t randomShipSizeY = ((0xF & (0x74 ^ conversionValues[2])) % 3) + 1;
            uint8_t randomX = ((0xF & (0x3C ^ conversionValues[0])) & 0xF) % (MAX_X - randomShipSizeX + 1);
            // u served us well
            /*
            int8_t initialX = list.empty ?
                1 : 
                117 + list.first->obj.ulx;
            initialX %= (MAX_X - shipSize);
            */
            addToList(
                &list,
                createGameObject(
                    randomX,
                    -randomShipSizeY + 1,
                    randomShipSizeX,
                    randomShipSizeY
                )
            );
            moves = 0;
        }
        frames = 0;
    }
    
    // compute next framebuffer (framebuffer should be all LED_DISABLE)
    // render life bar
    for (int x = MAX_X - 1; x > MAX_X - 1 - lives; x--) {
        // @ static y=0 coordinates
        if ((frames % framesMax) < (framesMax / 2))
            framebuffer[0][x] = LED_ENABLE;
    }
    
    // render game objects
    Node *node = list.first;
    while (node != NULL) {
        for (int y = node->obj.uly; y <= node->obj.lry; y++) {
            for (int x = node->obj.ulx; x <= node->obj.lrx; x++) {
                if(x >= 0 && y >= 0 && x < MAX_X && y < MAX_Y)
                    framebuffer[y][x] = LED_ENABLE;
            }
        }
        
        // iterate
        node = node->next;
    }
}

void checkCollision(int8_t y, int8_t x) {
    Node *node = list.first;
    while (node != NULL) {
        // advanced collision detection
        if (x >= node->obj.ulx && x <= node->obj.lrx &&
            y >= node->obj.uly && y <= node->obj.lry)
            removeFromList(&list, node);
        
        // iterate
        node = node->next;
    }
}

void renderGameOver(void) {
    // loop over all rows
    for (int y = 0; y < GO_SCREEN_SIZE; y++) {
        // query texture
        uint16_t row = GOScreen[y];
        // iterate all x
        for (int x = MAX_X - 1; x >= 0; x--) {
            uint8_t active = 0x1 & row;
            row >>= 1;
            // finally render
            if (active && (frames++ % GO_SCREEN_FLASHING_PERIOD) < GO_SCREEN_FLASHING_COMPARE)
                framebuffer[y + GO_SCREEN_OFFSET_Y][x] = LED_ENABLE;
            else
                framebuffer[y + GO_SCREEN_OFFSET_Y][x] = LED_DISABLE;
        }
    }
}

GameObject createGameObject(int8_t x, int8_t y, uint8_t w, uint8_t h) {
    GameObject go;
    go.ulx = x;
    go.uly = y;
    go.lrx = x + w - 1;
    go.lry = y + h - 1;
    return go;
}

Node *createNode(GameObject obj) {
    Node *node = (Node*) malloc(sizeof(Node));
    if (node == NULL)
        GPIO_SetBits(GPIOA, GPIO_Pin_5);
    node->obj = obj;
    node->next = NULL;
    node->prev = NULL;
    return node;
}

void createLinkedList(void) {
    list.first = NULL;
    list.empty = 1;
}

void addToList(LinkedList* list, GameObject obj) {
    if (list->first == NULL) {
        list->empty = 0;
        list->first = createNode(obj);
    // first and last should both be set
    } else {
        Node* newFirst = createNode(obj);
        newFirst->next = list->first;
        list->first->prev = newFirst;
        list->first = newFirst;
    }
}

void removeFromList(LinkedList* list, Node *node) {
    // special case for first node
    if (list->first == node) {
        list->first = node->next;
    }
    
    // check if list is empty now
    if (list->first == NULL) {
        list->empty = 1;
    }
    
    // remove in O(1)
    if (node != NULL) {
        if (node->prev != NULL && node->prev->next != NULL)
            node->prev->next = node->next;
        if (node->next != NULL && node->next->prev != NULL)
            node->next->prev = node->prev;
    
        // never forget
        free(node);
    }
}
